
<!DOCTYPE html>
<html lang="zh-cn">
<head>
    <title>NES游戏开发</title>
    <link rel="stylesheet" href="index.css"/>
</head>

<body>

<div class="main">
<p><h1>08、背景 碰撞</h1>
</p><p>背景和精灵有些区别，我们无法读取PPU中的字节。
</p><p>我们先搞个地图，每个块大小是 16x16，填充整个背景需要 240字节的数组。X从0-15，Y从0-14。我们把数组放到RAM中，以防止被修改。
</p><p>后面演示运行情况，先看下数组结构。
</p><p>
</p><p><pre>
</p><p>const unsigned char c2 [] = {
</p><p>0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0，
</p><p>0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0，
</p><p>0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0，
</p><p>0,0,0,0,0,1,0,0,1,0,0,0,0,0,0,0，
</p><p>0,0,0,0,0,1,0,0,1,0,0,0,0,0,0,0，
</p><p>0,0,0,0,0,1,0,0,1,0,0,0,0,0,0,0，
</p><p>0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0，
</p><p>0,0,0,1,0,0,0,0,0,0,1,0,0,0,0,0，
</p><p>0,0,0,0,1,0,0,0,0,1,0,0,0,0,0,0，
</p><p>0,0,0,0,0,1,1,1,1,0,0,0,0,0,0,0，
</p><p>0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0，
</p><p>0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0，
</p><p>0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0，
</p><p>0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0，
</p><p>0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
</p><p>};
</p><p></pre>
</p><p>
</p><p>1表示游戏中的块
</p><p>
</p><p><img src="img/08_1.png">
</p><p>
</p><p>要检测碰撞，你只需要将X和Y的低字节去掉，然后拼到一起，后面解释方法
</p><p>X>>4 将X向右挪4位，Y & 0xF0 将Y的低4位变成0，用他和数组中的值逐个对比，1碰撞，0没有
</p><p>
</p><p>我会检测精灵的4个方向，并在碰撞的时候修改U D L R 4个属性值（就是精灵的上下左右方向是否有碰撞）
</p><p>我先进行X移动，检查是否碰撞，如果碰撞了则跳，再移动Y，检测是否碰撞，如果碰撞则跳过，也就是不执行移动的动作。具体看函数bg_collision()
</p><p>
</p><p>这段代码应该精简，因为每一帧都会有XY的变动，我会修改代码实现变速。每帧1个像素太慢了，会造成很差的游戏体验。
</p><p>
</p><p>我习惯使用 Tiled Map 制作游戏关卡，他使用非常简单，并且可以导出csv文件，这样可以方便制作C的数组。但是这个工具无法导入NES格式的.chr文件，所以我不得不重做所有类型的地图块。这很简单，我们只需要2个种，空白块和普通的地图块。
</p><p>
</p><p>我先用NES Screen Tool制作图块，然后绘制2x2的块。看起来这样的做法有点蠢，但是当你的游戏里有几十种图块的时候，这样做起来就会很happy了
</p><p>因为没有办法导出nametable的图片，所以我截图然后在GIMP编辑，另存为metatile.png
</p><p>以tileset的方式导入 Tiled Map地图编辑器中。因为nes屏幕工具会使像素加倍，所以每个图片的尺寸都是32x32
</p><p>（翻译备注：意思就是编辑器截图图像比较大，不是8x8的，而是32x32的，所以才会有上面这种情况）
</p><p>编辑完关卡地图，导出csv文件。
</p><p>
</p><p><img src="img/08_2.png">
</p><p>
</p><p>SV文件转换为C数组是件小事儿，但是我还是做了个python3的脚本来实现。我将C数组导入我的代码，然后使用一个指针的数组指向前面的数组。
</p><p>
</p><p><pre>
</p><p>#include “CSV/c1.c”
</p><p>#include “CSV/c2.c”
</p><p>#include “CSV/c3.c”
</p><p>#include “CSV/c4.c”
</p><p>
</p><p>const unsigned char * const All_Collision_Maps[] = {c1,c2,c3,c4};
</p><p></pre>
</p><p>
</p><p>现在，我编写了一些代码，将一个2×2块的块打印到屏幕上，使用了一个大的循环，里面用了vram_put()方法。
</p><p>使用vram_put()前需要先关闭屏幕，写入PPU是以从左到右，然后下一行开始从左到右。这样你可以很简单的就写完整个屏幕。
</p><p>
</p><p>Start按键可以切换下一个地图显示
</p><p>
</p><p>当你按右的时候，X的位置会+1，然后检测4个碰撞点，如果发生碰撞则会结束向右移动（X会-1）
</p><p>（翻译备注：作者用 坐标变换后的点去检测碰撞，发生碰撞就退回变化，如果没有碰撞，精灵就会按照变化后的点移动--也就是重新绘制精灵新的位置）
</p><p>
</p><p>测试：按方向撞墙，碰撞可以生效。按Start按钮，背景变化，碰撞依旧有效，因为RAM加载了一个新的地图碰撞检测。
</p><p>
</p><p>注意：我使用屏幕滚动的方式将整个背景向下移动了1个像素。Y scroll=ff(-1)，因为精灵总是会向下移动一个像素，所以必须将背景整体下移1个像素。
</p><p>
</p><p>https://github.com/nesdoug/09_BG_Collide/blob/master/collide.c
</p><p>
</p><p>https://github.com/nesdoug/09_BG_Collide
</p><p>
</p><p>地图加载部分的代码写的不是很好，因为只能绘制一种贴图块，而且属性表不会修改，稍后我会介绍一个更好的加载方式（11、Metatiles），在此之前我先说一下滚动。
</p><p>
</p><p>
</p><p>
</p></div>
<script>
var h1 = document.getElementsByTagName("h1");
if(h1.length>0){
    document.title = h1[0].innerHTML;
}
</script>
</body>
</html>
